"""
    pylyricsgrabber - A small, simple, lightweight lyrics grabber
    built with cross platform compatibility, simplicity and code
    modularisation in mind.

    All details about this project can be found at:
    http://code.google.com/p/pylyricsgrabber/

    Copyright (c) 2008 Ryan Kirkman
"""

"""
The MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""

import sgmllib, string, urllib
from Tkinter import *
import tkMessageBox
import tkFileDialog

#-- SOURCED FROM: http://code.activestate.com/recipes/52281/

class StrippingParser(sgmllib.SGMLParser):

    # These are the HTML tags that we will leave intact
    #valid_tags = ('b', 'a', 'i', 'br', 'p')
    valid_tags = ()

    from htmlentitydefs import entitydefs # replace entitydefs from sgmllib
    
    def __init__(self):
        sgmllib.SGMLParser.__init__(self)
        self.result = ""
        self.endTagList = [] 
        
    def handle_data(self, data):
        if data:
            self.result = self.result + data

    def handle_charref(self, name):
        self.result = "%s&#%s;" % (self.result, name)
        
    def handle_entityref(self, name):
        if self.entitydefs.has_key(name): 
            x = ';'
        else:
            # this breaks unstandard entities that end with ';'
            x = ''
        self.result = "%s&%s%s" % (self.result, name, x)
    
    def unknown_starttag(self, tag, attrs):
        """ Delete all tags except for legal ones """
        if tag in self.valid_tags:       
            self.result = self.result + '<' + tag
            for k, v in attrs:
                if string.lower(k[0:2]) != 'on' and string.lower(v[0:10]) != 'javascript':
                    self.result = '%s %s="%s"' % (self.result, k, v)
            endTag = '</%s>' % tag
            self.endTagList.insert(0,endTag)    
            self.result = self.result + '>'
                
    def unknown_endtag(self, tag):
        if tag in self.valid_tags:
            self.result = "%s</%s>" % (self.result, tag)
            remTag = '</%s>' % tag
            self.endTagList.remove(remTag)

    def cleanup(self):
        """ Append missing closing tags """
        for j in range(len(self.endTagList)):
                self.result = self.result + self.endTagList[j]    

#-- END SOURCED FROM

class Model:
    def __init__(self):
        self.lyricList = []

    #-- SOURCED FROM: http://code.activestate.com/recipes/52281/
    def tagStrip(self, s):
        """ Strip illegal HTML tags from a string.

        strip(string) -> string
        """
        parser = StrippingParser()
        parser.feed(s)
        parser.close()
        parser.cleanup()
        return parser.result
    #-- END SOURCED FROM
    
    def contentList(self, url):
        """ Grabs a HTML page and returns a stripped list
            of all content.

        contentList(url) -> list[string]
        """
        f = urllib.urlopen(url)
        stripped = self.tagStrip(f.read())
        f.close()
        content = stripped.split("\r\n")
        return content

    def grabLyricList(self, url):
        """ Gets the lyrics from an azlyrics URL.

        getLyricList(string) -> None
        """
        #-- If the URL is "" then it means no results were
        #-- found, so send a notification back.
        if url == "":
            self.lyricList = ["No results found. Please try again."]
        else:
            lyricArray = self.contentList(url)

            self.lyricList = max(lyricArray, key=len)

    def getLyricList(self):
        """ Returns the current lyric list.

        getLyricList() -> list[string]
        """
        return self.lyricList

    def searchFor(self, searchTerm):
        """ Get a URL based on search terms provided.

        searchResults(string) -> None
        """
        #-- Generate a list of search terms
        searchWords = searchTerm.strip().split()
        
        url = "http://search.azlyrics.com/search.php?q="
        for i in searchWords:
            url += i + "+"
        url = url[:-1]

        #-- Get the page contents
        page = self.contentList(url)
        songURL = ""

        #-- Loop through the list until a valid link is found.
        for i in page:
            if i.find("http://www.azlyrics.com/lyrics/") != -1:
                songURL = i
                break
        
        self.grabLyricList(songURL)

    def saveToFile(self, newFile):
        """ Saves current lyrics to a file.
            Currently just overwrites everything in the file.

        saveToFile(string) -> None
        """

        #-- Check to see if file exists
        if newFile:
            newFile.writelines(map(lambda x: x + "\n", self.lyricList))
            newFile.close()

    def loadFromFile(self, lyricFile):
        """ Loads lyrics from a given file.

        Model.loadFromFile(string) -> None
        """
        self.lyricList = []

        #-- Check to see if file exists
        if lyricFile:
            for line in lyricFile:
                self.lyricList.append(line.strip())
            lyricFile.close()

    def __iter__(self):
        """ Make the model class iterable. """
        for i in self.lyricList:
            yield i

class Controller():
    def __init__(self, master):
        self.lyricList = Model()

        #-- Do GUI initialisation.
        self.master = master

        #-- Create a frame.
        frame = Frame(master, bd=2, relief=SUNKEN)
        master.bind("<Return>", self.search)
        frame.pack()

        #frame.grid_rowconfigure(0, weight=1)
        #frame.grid_columnconfigure(0, weight=1)
        
        #-- Make the file menu
        fileMenuButton = Menubutton(frame, text='File')
        fileMenuButton.grid(row = 1, column = 0, sticky = W, padx = 0)
        fileMenu = Menu(fileMenuButton)
        fileMenu.add_command(label='Open Lyrics File',
                             command = self.openLyrics)
        fileMenu.add_command(label='Save Lyrics File',
                             command = self.saveLyrics)
        fileMenu.add_command(label='Quit', command=self.master.destroy)
        fileMenuButton.config(menu=fileMenu)

        #-- Help menu
        helpMenuButton = Menubutton(frame, text='Help', underline=0)
        helpMenuButton.grid(row = 1, column = 0, sticky = W, padx = 30)
        helpMenu = Menu(helpMenuButton)
        helpMenu.add_command(label='Instructions',
                             command = self.showInstructionBox)
        helpMenu.add_command(label='About',
                             command = self.showAboutBox)
        helpMenuButton.config(menu=helpMenu)

        #-- Make the search label
        self.myLabel = Label(frame, text = "Search Terms: ")
        self.myLabel.grid(row = 2, column = 0, pady = 4)

        #-- Make the search entry box.
        self.myEntry = Entry(frame, width = 50)
        self.myEntry.grid(row = 2, column = 1, pady = 4)

        #-- Set focus to the search bar
        self.myEntry.focus()

        #-- Make the search button.
        self.myButton = Button(frame, text="Search", command = self.search)
        self.myButton.grid(row = 2, column = 2, pady = 4)

        #-- Make the view that will display the lyrics.
        self.view = View(frame, self.lyricList)
        self.view.configure(state=DISABLED,
                            height = 30,
                            width = 88,
                            bd = 2,
                            pady = 10,
                            padx = 10,
                            foreground = "black")
        self.view.grid(row = 3, columnspan = 3, pady = 4)

        #-- Make the scrollbar.
        self.yscrollbar = Scrollbar(frame)
        self.yscrollbar.grid(row=3, sticky=N+S, column = 3, pady = 4)

        #-- Attach the scrollbar.
        self.view.config(yscrollcommand = self.yscrollbar.set)
        self.yscrollbar.config(command=self.view.yview)

    def search(self, event=None):
        """ Search for lyrics based on the currently
            entered search terms.

        search() -> None
        """
        self.lyricList.searchFor(self.myEntry.get())
        self.view.update()

    def openLyrics(self):
        """ Generate an open dialog.

        Controller.open() -> None
        """
        lyricsFile = tkFileDialog.askopenfile(mode="r")
        self.lyricList.loadFromFile(lyricsFile)
        self.view.update()

    def saveLyrics(self):
        """ Generate a save dialog.

        Controller.save() -> None
        """
        saveFile = tkFileDialog.asksaveasfile(mode="w")
        self.lyricList.saveToFile(saveFile)

    def showAboutBox(self):
        """ Shows the about box.

        showAboutBox() -> None
        """
        tkMessageBox.showinfo("About", "by Ryan Kirkman," \
                              " for your lyrical enjoyment.")

    def showInstructionBox(self):
        """ Pops up a message box telling the user
            how to use the program.

        showInstructionBox() -> None
        """
        msg = "To use Lyric Grabber, simply type in\n"
        msg += " the name of the song and/or artist\n"
        msg += " and press Enter or click \"Search\"."
        tkMessageBox.showinfo("Instructions", msg)
                              

class View(Text):
    def __init__(self, master, lyricList):
        Text.__init__(self, master)
        self.lyricList = lyricList

    def update(self):
        """ Refresh the contents of the view class.

        update() -> None
        """
        self.config(state=NORMAL)
        self.delete('1.0', END)
        self.insert(END, self.lyricList.getLyricList())
        self.config(state=DISABLED)
        

class LyricsApp():
    """The Lyrics Application"""

    def __init__(self, master=None):
        master.title("Lyrics Grabber")
        self.controller = Controller(master)

def main():
    root = Tk()
    global app
    app = LyricsApp(root)
    root.mainloop()
    
if  __name__ == '__main__':
    main()
